<script lang="ts" setup>
import { useRoute } from 'vue-router'
import dayjs from 'dayjs'
import { type TablePaginationConfig, message } from 'ant-design-vue'
import type { SorterResult } from 'ant-design-vue/es/table/interface'
import { computed, onBeforeUnmount, onMounted, provide, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { orderMap } from './config'
import CveTable from './components/CveTable.vue'
import type { HostInfoInCve } from './components/CveTask.vue'
import type { Cve, CveTableItem, Direction, HostDetail, QueryCvesWithHostParams, Rpm } from '@/api'
import PageWrapper from '@/components/PageWrapper.vue'
import { api } from '@/api'

const route = useRoute()
const { t } = useI18n()

const isInfoLoading = ref(false)
const hostId = ref<string>('')

// #region --------------------------------< host detail >-----------------------------------
const hostInfo = ref<HostDetail>({
  host_ip: '',
  host_group: '',
  host_name: '',
  last_scan: 0,
  repo_id: '',
  repo_name: '',
  unaffected_cve_num: 0,
  affected_cve_num: 0,
  fixed_cve_num: 0,
  unfixed_cve_num: 0,
  cluster_id: '',
  cluster_name: '',
})

async function queryHostInfo(hostId: string) {
  isInfoLoading.value = true
  const [, res] = await api.getHostInfo(hostId)
  if (!res)
    return
  hostInfo.value = res
  isInfoLoading.value = false
}

const scaningHosts = ref<string[]>([])

async function handleScan() {
  if (scaningHosts.value.length > 0)
    return
  const selected = [hostId.value!]
  const params: Record<string, { host_list: string[] }> = {}
  params[hostInfo.value.cluster_id] = { host_list: [hostId.value!] }
  const [_] = await api.scanHost(params)
  if (!_)
    queryHostStatus(selected)
}

const isScaning = ref(false)
let timer: NodeJS.Timer

async function queryHostStatus(hostList: string[]) {
  const parseHostStatus = (hostStatus: Record<string, number>) => {
    const scanHostList: string[] = []
    Object.keys(hostStatus).forEach((key) => {
      if (hostStatus[key] === 3)
        scanHostList.push(key)
    })
    return scanHostList
  }
  const [, res] = await api.getHostStatus(hostList)
  if (res) {
    scaningHosts.value = parseHostStatus(res.result)
    if (scaningHosts.value.length > 0) {
      isScaning.value = true
      timer = setTimeout(() => {
        queryHostStatus(scaningHosts.value)
      }, 5000)
    }
    else {
      if (isScaning.value)
        queryHostInfo(hostId.value!)
      isScaning.value = false
      timer ?? clearTimeout(timer)
    }
  }
}

// #endregion

// #region --------------------------------< cve table >-----------------------------------
const cveTableData = ref<Cve[]>([])

const isCveFixed = ref(false)

const filterMap: Record<string, string[]> = reactive({
  severity: [],
})

const cveTableColumns = [
  {
    key: 'cve_id',
    dataIndex: 'cve_id',
    title: 'CVE_ID',
  },
  {
    key: 'publish_time',
    dataIndex: 'publish_time',
    title: t('vul.cves.publishTime'),
    sorter: true,
  },
  {
    key: 'package',
    dataIndex: 'package',
    title: t('vul.cves.package'),
  },
  {
    key: 'severity',
    dataIndex: 'severity',
    title: t('vul.cves.severity'),
    filters: [
      {
        text: t('vul.severityStatus.critical'),
        value: 'Critical',
      },
      {
        text: t('vul.severityStatus.high'),
        value: 'High',
      },
      {
        text: t('vul.severityStatus.medium'),
        value: 'Medium',
      },
      {
        text: t('vul.severityStatus.low'),
        value: 'Low',
      },
      {
        text: t('vul.severityStatus.unknown'),
        value: 'Unknown',
      },
    ],
  },
  {
    key: 'cvss_score',
    dataIndex: 'cvss_score',
    title: t('vul.cves.cvssScore'),
    sorter: true,
  },
]

const tableState = reactive<{
  selectedRowKeys: string[]
  loading: boolean
}>({
  selectedRowKeys: [],
  loading: false,
})

const pagination = reactive({
  total: 0,
  current: 1,
  pageSize: 10,
  showTotal: (total: number) => t('common.total', { count: total }),
  showSizeChanger: true,
  pageSizeOptions: ['10', '20', '30', '40'],
})

const expandTableState = reactive<{
  selectedRowKeys: string[]
  getCheckboxProps: () => { disabled: boolean }
}>({
      selectedRowKeys: [],
      getCheckboxProps: () => {
        return {
          disabled: isCveFixed.value,
        }
      },
    })

const hostInfoInCve = computed<HostInfoInCve>(() => {
  const { host_ip, host_name, cluster_id, cluster_name } = hostInfo.value
  return {
    host_id: hostId.value || '',
    host_ip,
    host_name,
    cluster_id,
    cluster_name,
  }
})
/**
 * query cve list with host id
 */
async function queryCvesWithHost(searchKey?: string, sortKey?: string, sortDir?: Direction) {
  tableState.loading = true
  tableState.selectedRowKeys = []
  expandTableState.selectedRowKeys = []
  const params: QueryCvesWithHostParams = {
    page: pagination.current,
    per_page: pagination.pageSize,
    host_id: hostId.value!,
    filter: {
      affected: true,
      fixed: isCveFixed.value,
      search_key: searchKey || undefined,
      severity: filterMap.severity,
    },
    sort: sortKey,
    direction: sortDir,
  }
  const [, res] = await api.getCvesWithHost(params)
  if (res) {
    cveTableData.value = res.result
    pagination.total = res.total_count
  }
  tableState.loading = false
}

/**
 * select one of cves include this cve's all rpms
 */
function onSelect(record: CveTableItem, selected: boolean) {
  if (selected) {
    tableState.selectedRowKeys.push(record.cve_id)
    if (!isCveFixed.value && record.rpms)
      selectAllExpandRow(record.rpms)
  }
  else {
    tableState.selectedRowKeys = tableState.selectedRowKeys.filter(
      item => item !== record.cve_id,
    )
    record.rpms && cancelAllExpandRow(record.rpms)
  }
}

/**
 * select all cve include every cve's all rpms
 */
function onSelectAll(selected: boolean, selectedRows: CveTableItem[]) {
  tableState.selectedRowKeys = selectedRows.map(item => item.cve_id)

  if (selected) {
    tableState.selectedRowKeys.forEach((key) => {
      const record = cveTableData.value.find(item => item.cve_id === key)!
      if (!isCveFixed.value && record.rpms)
        selectAllExpandRow(record.rpms)
    })
  }
  else {
    expandTableState.selectedRowKeys = []
  }
}

/**
 * expand specified cve to show rpm infos
 */
async function handleExpand(expanded: boolean, record: CveTableItem) {
  if (!expanded)
    return
  await queryRpms(record.cve_id, hostId.value!)
  if (tableState.selectedRowKeys.includes(record.cve_id)) {
    if (!isCveFixed.value && record.rpms)
      selectAllExpandRow(record.rpms)
  }
}

/**
 * table changed, include pageation、filters、sorter
 */
function handleTableChange(page: TablePaginationConfig, filters: Record<string, string[] | null>, sorter: SorterResult<CveTableItem>, searchKey?: string) {
  page.current && (pagination.current = page.current)
  page.pageSize && (pagination.pageSize = page.pageSize)
  filterMap.severity = filters.severity ?? []
  if (sorter.column) {
    const sortKey = sorter.columnKey
    const sortDir = sorter.order
    queryCvesWithHost(searchKey, sortKey as string, orderMap[sortDir as Direction])
    return
  }
  queryCvesWithHost(searchKey)
}

function handleRadioChange(checked: boolean) {
  isCveFixed.value = checked
  queryCvesWithHost()
}

/**
 * Query rpm list through cve id and host id and insert list into specified cve object
 * @param cveId
 * @param hostId
 */
async function queryRpms(cveId: string, hostId: string): Promise<void> {
  if (!cveTableData.value)
    return
  const cve = cveTableData.value.find(item => item.cve_id === cveId)
  if (!cve || cve.rpms)
    return
  if (!cve || cve.rpms)
    return
  if (!isCveFixed.value) {
    const [, res] = await api.getUnfixedRpm({ cve_id: cveId, host_ids: [hostId] })
    if (res) {
      cve.rpms = res.map(item => ({
        ...item,
        cve_id: cve.cve_id,
      }))
    }
  }
  else {
    const [, res] = await api.getFixedRpm({ cve_id: cveId, host_ids: [hostId] })
    if (res) {
      cve.rpms = res.map(item => ({
        ...item,
        cve_id: cve.cve_id,
      }))
    }
  }
}

// #endregion

// #region ----------------------------------------< expand rpm table >----------------------------------------
const expandTableColumns = computed(() =>
  !isCveFixed.value
    ? [
        {
          key: 'installed_rpm',
          dataIndex: 'installed_rpm',
          title: t('vul.cves.unfixed.rpms.installedRpm'),
        },
        {
          key: 'available_rpm',
          dataIndex: 'available_rpm',
          title: t('vul.cves.unfixed.rpms.availableRpm'),
        },
        {
          key: 'support_way',
          dataIndex: 'support_way',
          title: t('vul.cves.supportWay'),
        },
        {
          key: 'host_num',
          dataIndex: 'host_num',
          title: t('vul.cves.hostNum'),
        },
      ]
    : [
        {
          key: 'installed_rpm',
          dataIndex: 'installed_rpm',
          title: t('vul.cves.fixed.rpms.installedRpm'),
        },
        {
          key: 'fixed_way',
          dataIndex: 'fixed_way',
          title: t('vul.cves.supportWay'),
        },
        {
          key: 'host_num',
          dataIndex: 'host_num',
          title: t('vul.cves.hostNum'),
        },
      ],
)

/**
 * select specify rpm all row
 */
function selectAllExpandRow(rpms: Rpm[]) {
  rpms.forEach((item) => {
    const key = `${item.cve_id}-${item.installed_rpm}-${item.available_rpm}`
    expandTableState.selectedRowKeys.push(key)
  })
}

/**
 * cancel specify rpm all row check
 */
function cancelAllExpandRow(rpms: Rpm[]) {
  rpms.forEach((item) => {
    const key = `${item.cve_id}-${item.installed_rpm}-${item.available_rpm}`
    expandTableState.selectedRowKeys = expandTableState.selectedRowKeys.filter(
      item => item !== key,
    )
  })
}

/**
 * dealing with embedded list selection when slected one of options
 */
function onExpandSelect(record: Required<Rpm>, selected: boolean, selectedRows: Rpm[]) {
  const key = `${record.cve_id}-${record.installed_rpm}-${record.available_rpm}`
  selected
    ? expandTableState.selectedRowKeys.push(key)
    : (expandTableState.selectedRowKeys = expandTableState.selectedRowKeys.filter(
        item => item !== key,
      ))
  if (selected) {
    !tableState.selectedRowKeys.includes(record.cve_id)
    && tableState.selectedRowKeys.push(record.cve_id)
  }
  else {
    selectedRows.length === 0
    && (tableState.selectedRowKeys = tableState.selectedRowKeys.filter(
      item => item !== record.cve_id,
    ))
  }
}
/**
 * dealing with embedded list selection when slected all options
 */
function onExpandSelectAll(selected: boolean, _selectedRows: Rpm[], changeRows: Required<Rpm>[]) {
  const cveId = changeRows[0].cve_id
  if (selected)
    !tableState.selectedRowKeys.includes(cveId) && tableState.selectedRowKeys.push(cveId)
  else
    tableState.selectedRowKeys = tableState.selectedRowKeys.filter(item => item !== cveId)

  changeRows.forEach((item) => {
    const key = `${item.cve_id}-${item.installed_rpm}-${item.available_rpm}`
    if (selected) {
      expandTableState.selectedRowKeys.push(key)
    }
    else {
      expandTableState.selectedRowKeys = expandTableState.selectedRowKeys.filter(
        item => item !== key,
      )
    }
  })
}

const isExportint = ref(false)
async function exportCve() {
  isExportint.value = true
  const [_] = await api.exportCveInfo([hostId.value!])
  if (_)
    message.error(t('common.downloadFail'))

  isExportint.value = false
}

onBeforeUnmount(() => {
  if (timer)
    clearTimeout(timer)
})

provide('hostId', hostId.value)

// #endregion
onMounted(() => {
  hostId.value = route.params.host_id as string
  queryHostInfo(hostId.value)
  queryCvesWithHost()
  queryHostStatus([hostId.value])
})
</script>

<template>
  <PageWrapper>
    <a-card>
      <a-spin :spinning="isInfoLoading">
        <div class="host-detail">
          <h1>{{ hostInfo.host_name }}</h1>
          <a-row>
            <a-col span="8">
              <p>{{ `${t('vul.hostGroup')} : ${hostInfo.host_group || ''}` }}</p>
            </a-col>
            <a-col span="8">
              <p>{{ `${t("vul.unfixedCve")}： ${hostInfo.affected_cve_num}` }}</p>
            </a-col>
            <a-col span="8">
              <p>{{ `CVE REPO： ${hostInfo.repo_name || t('vul.notSet')}` }}</p>
            </a-col>
            <a-col span="8">
              <p>{{ `${t('vul.ip')} : ${hostInfo.host_ip || ''}` }}</p>
            </a-col>
            <a-col span="8">
              <p>{{ `${t("vul.fixedCve")} ： ${hostInfo.fixed_cve_num}` }}</p>
            </a-col>
            <a-col span="8">
              <p>
                {{
                  `${t('vul.lastScan')}： ${
                    hostInfo.last_scan
                      ? dayjs(hostInfo.last_scan * 1000).format('YYYY-MM-DD HH:mm:ss')
                      : t('vul.notScaned')
                  } `
                }}
              </p>
            </a-col>
            <a-col span="8">
              <a-space>
                <a-button type="primary" :loading="scaningHosts.length !== 0" @click="handleScan">
                  {{
                    scaningHosts.length === 0 ? t('vul.scan') : t('vul.scanning')
                  }}
                </a-button>
                <a-button type="primary" :loading="isExportint" @click="exportCve">
                  {{ t("vul.exportCve") }}
                </a-button>
              </a-space>
            </a-col>
            <a-col span="8">
              <p>{{ `${t("vul.unaffectedCve")}： ${hostInfo.unaffected_cve_num}` }}</p>
            </a-col>
            <a-col span="8">
              <p>{{ `${t("vul.kernelChangesAfterReboot")}： ${hostInfo.reboot ? t('common.yes') : t('common.no')}` }}</p>
            </a-col>
          </a-row>
        </div>
      </a-spin>
    </a-card>

    <a-card>
      <h1>CVEs</h1>
      <CveTable
        :is-fixed="isCveFixed"
        :table-columns="cveTableColumns"
        :table-data="cveTableData"
        :host-info="hostInfoInCve"
        :expand-table-columns="expandTableColumns"
        :pagination="pagination"
        :loading="tableState.loading"
        :row-selection="{
          selectedRowKeys: tableState.selectedRowKeys,
          onSelect,
          onSelectAll,
        }"
        :expand-row-selection="{
          selectedRowKeys: expandTableState.selectedRowKeys,
          onSelect: onExpandSelect,
          onSelectAll: onExpandSelectAll,
          getCheckboxProps: expandTableState.getCheckboxProps,
        }"
        @expand="handleExpand"
        @change="handleTableChange"
        @radio-change="handleRadioChange"
        @clear="
          tableState.selectedRowKeys = [];
          expandTableState.selectedRowKeys = [];
        "
      />
    </a-card>
  </PageWrapper>
</template>

<style lang="less" scoped>
.host-detail {
  padding: 12px 20px;
  h1 {
    font-size: 25px;
    font-weight: bold;
  }
}
</style>
